package com.heima.search.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.heima.common.exception.LogicException;
import com.heima.common.util.UserInfoThreadLocal;
import com.heima.model.common.enums.AppHttpCodeEnum;
import com.heima.model.search.dto.UserSearchDto;
import com.heima.model.search.pojo.SearchHistory;
import com.heima.search.mapper.SearchMapper;
import com.heima.search.service.SearchService;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.elasticsearch.search.suggest.Suggest;
import org.elasticsearch.search.suggest.SuggestBuilder;
import org.elasticsearch.search.suggest.SuggestBuilders;
import org.elasticsearch.search.suggest.completion.CompletionSuggestion;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

@Service
public class SearchServiceImpl extends ServiceImpl<SearchMapper, SearchHistory> implements SearchService {

    @Autowired
    private RestHighLevelClient restHighLevelClient;

    @Autowired
    private SearchMapper searchMapper;

    // 创建搜索文档
    @Override
    public void save(String message) throws IOException {
//        JSONObject jsonObject = JSONUtil.parseObj(message);
//        // "id"
//        Long id = jsonObject.getLong("id");
        // 准备Request对象
        IndexRequest request = new IndexRequest("app_article_info").id(JSONUtil.parseObj(message).getStr("id"));
        // 准备请求体
        request.source(message, XContentType.JSON);
        // 发送请求
        restHighLevelClient.index(request, RequestOptions.DEFAULT);
    }

    // 查询文档
    @Override
    public List<Map<String, Object>> search(UserSearchDto dto) throws IOException {
        //1. 校验参数
        if (dto == null) {
            throw new LogicException(AppHttpCodeEnum.PARAM_REQUIRE);
        }

        // 如果dto有数据则表明搜索成功，开始封装搜索历史
        //保存搜索历史记录 : 用户登录状态且第一页的时候才需要保存
        if (UserInfoThreadLocal.get() != null && dto.getFromIndex() == 0) {
            searchMapper.insert(new SearchHistory(null,dto.getSearchWords(), Math.toIntExact(UserInfoThreadLocal.get().getId()),new Date()));
        }

        //最小时间校验
        Date minTime = dto.getMinBehotTime();
        minTime = minTime == null ? new Date() : minTime;
        //每页展示数据条数校验
        int size = dto.getPageSize();
        size = size <= 0 ? 10 : size;
        size = Math.min(size, 50);

        //2. 创建ES搜索请求对象
        SearchRequest request = new SearchRequest("app_article_info");
        //3. 设置DSL参数
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        //3.1 搜索关键词参数
        if (StrUtil.isNotEmpty(dto.getSearchWords())) {
            boolQuery.must(QueryBuilders.matchQuery("all", dto.getSearchWords()));
        } else {
            boolQuery.must(QueryBuilders.matchAllQuery());
        }

        //3.2 发布时间参数(根据上一页的最后一个文章的发布时间查询下一页数据)
        boolQuery.filter(QueryBuilders.rangeQuery("publishTime").lt(minTime));
        //设置查询条件
        request.source().query(boolQuery);
        //3.3 分页参数
        request.source().from(0).size(size);
        //3.4 排序参数
        request.source().sort("publishTime", SortOrder.DESC);
        //3.5 高亮参数
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder.field("title");
        highlightBuilder.preTags("<font style='color:red; font-size: inherit;'>");
        highlightBuilder.postTags("</font>");
        highlightBuilder.requireFieldMatch(false);

        request.source().highlighter(highlightBuilder);

        //4. 发送请求, 获取响应
        SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);
        //5. 解析响应结果
        //5.1 获取命中数据
        SearchHits hits = response.getHits();
        //5.2 获取命中数据列表
        SearchHit[] searchHits = hits.getHits();
        //5.3 遍历循环获取每一条命中数据
        List<Map<String, Object>> mapList = Arrays.stream(searchHits).map(searchHit -> {
            //获取命中数据的原始文档内容
            Map<String, Object> source = searchHit.getSourceAsMap();
            source.put("h_title", source.get("title"));
            //获取文档的高亮结果集
            Map<String, HighlightField> highlightFields = searchHit.getHighlightFields();
            if (!CollectionUtil.isEmpty(highlightFields)) {
                //获取指定字段名称的高亮结果
                HighlightField titleField = highlightFields.get("title");
                //判断对应字段的高亮结果集是否为空
                if (titleField != null) {
                    //获取高亮文本片段
                    Text[] texts = titleField.fragments();
                    String highlightResult = StrUtil.join("", texts);
                    //封装高亮文本片段到原始文档, 返回
                    source.put("h_title", highlightResult);
                }
            }
            return source;
        }).collect(Collectors.toList());

        return mapList;
    }

    /**
     * 历史搜索展示
     *
     * @param dto
     * @return
     * @throws IOException
     */
    @Override
    public List<SearchHistory> searchHistory(UserSearchDto dto) {
        if (UserInfoThreadLocal.get() != null && dto.getFromIndex() == 0) {
            //3、执行分页搜索，只查询最近10条记录
            Page<SearchHistory> paged = this.page(new Page<>(1, 10), Wrappers.<SearchHistory>lambdaQuery()
                    .eq(SearchHistory::getUserId, UserInfoThreadLocal.get().getId())
                    .orderByDesc(SearchHistory::getCreatedTime));
            return paged.getRecords();
        }
        return Collections.emptyList();
    }

    /**
     * 搜索自动补全
     *
     * @param dto
     * @return
     * @throws IOException
     */
    @Override
    public List<String> autoCompletion(UserSearchDto dto) throws IOException {
        // 1.准备Request
        SearchRequest request = new SearchRequest("app_article_info");
        // 2.准备DSL
        request.source().suggest(new SuggestBuilder().addSuggestion(
                //给当前搜索取个别名
                "suggestions",
                SuggestBuilders
                        //设置当前搜索的字段
                        .completionSuggestion("suggestion")
                        //设置搜索的关键词
                        .prefix(dto.getSearchWords())
                        //跳过重复数据
                        .skipDuplicates(true)
                        .size(10)
        ));
        // 3.发起请求
        SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);
        // 4.解析结果
        Suggest suggest = response.getSuggest();
        // 4.1.根据补全查询名称，获取补全结果
        CompletionSuggestion suggestions = suggest.getSuggestion("suggestions");
        // 4.2.获取options
        List<CompletionSuggestion.Entry.Option> options = suggestions.getOptions();
        // 4.3.遍历获取补全结果
        return options.stream().map(option -> option.getText().toString()).collect(Collectors.toList());
    }
}
